﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Vision.Core.Components
{
    /// <summary>
    /// 
    /// </summary>
    public class ActivityScheduler : IDisposable
    {
        private EnterpriseServicesDb DataAccess { get; set; }
                
        /// <summary>
        /// 
        /// </summary>
        /// <param name="catalogConnectionString"></param>
        public ActivityScheduler(string catalogConnectionString)
        {
            this.DataAccess = new EnterpriseServicesDb(catalogConnectionString);            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Id"></param>
        /// <returns></returns>
        public Entities.Scheduling.VisionActvitySchedule GetVisonScheduleById(int Id)
        {
            return this.VisionActvityScheduleRecordToVisionActvityScheduleEntity(this.DataAccess.ActivitySchedules.Where(s => s.ScheduleId == Id).Single());
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public Entities.Scheduling.VisionActvitySchedule GetVisonScheduleByKey(Guid key)
        {
            return this.VisionActvityScheduleRecordToVisionActvityScheduleEntity(this.DataAccess.ActivitySchedules.Where(s => s.Key == key).Single());
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="activityKey"></param>
        /// <returns></returns>
        public List<Entities.Scheduling.VisionActvitySchedule> GetVisionSchedules(Guid activityKey)
        {
            List<Entities.Scheduling.VisionActvitySchedule> schedules = new List<Entities.Scheduling.VisionActvitySchedule>();
            var scheduleRecords = this.DataAccess.ActivitySchedules.Where(s => s.ActivityKey == activityKey);
            foreach (var scheduleRecord in scheduleRecords){
                schedules.Add(this.VisionActvityScheduleRecordToVisionActvityScheduleEntity(scheduleRecord));
            }
            return schedules;   
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="schedule"></param>
        public void SaveVisonSchedule(Entities.Scheduling.VisionActvitySchedule schedule)
        {
            DateTime timeStamp = DateTime.Now;

            int count = (from a in this.DataAccess.ActivitySchedules where a.Key == schedule.Key select a).Count();
            if (count > 0){                                
                Entities.DataAccess.ActivitySchedule scheduleRecord = (from a in this.DataAccess.ActivitySchedules where a.Key == schedule.Key select a).Single();                
                scheduleRecord.Description = schedule.Description;
                scheduleRecord.ActivityKey = schedule.ActivityKey;
                scheduleRecord.ActivityQueue = schedule.ActivityQueue;
                scheduleRecord.ActivityParams = Common.Serialization.CreateXElementFromDoc(Common.Serialization.SerializeObjectToXml(schedule.ActivityParams));
                scheduleRecord.ScheduleType = (int)schedule.ScheduleType;
                scheduleRecord.ScheduleStartDate = schedule.ScheduleStartDate;
                scheduleRecord.ScheduleFinishDate = schedule.ScheduleFinishDate;
                scheduleRecord.ScheduleCanExpire = schedule.ScheduleCanExpire;
                scheduleRecord.RepeatEvery = schedule.RepeatEvery.Ticks;
                scheduleRecord.RunOnWeekDays = (int)schedule.RunOnWeekDays;
                scheduleRecord.RunOnMonths = (int)schedule.RunOnMonths;
                scheduleRecord.RunOnDays = (int)schedule.RunOnDays;
                scheduleRecord.RunOnDayOccurences = (int)schedule.RunOnDayOccurences;
                scheduleRecord.Active = schedule.Active;
                scheduleRecord.Modified = timeStamp;
                scheduleRecord.ScheduleNextRunDate = schedule.ScheduleNextRunDate;
                scheduleRecord.ScheduleLastRunDate = schedule.ScheduleLastRunDate;
                schedule.LastError = schedule.LastError;
                this.DataAccess.SubmitChanges();        
            }
            else {
                Entities.DataAccess.ActivitySchedule scheduleRecord = new Entities.DataAccess.ActivitySchedule();
                scheduleRecord.Key = schedule.Key;
                scheduleRecord.Description = schedule.Description;
                scheduleRecord.ActivityKey = schedule.ActivityKey;
                scheduleRecord.ActivityQueue = schedule.ActivityQueue;
                scheduleRecord.ActivityParams = Common.Serialization.CreateXElementFromDoc(Common.Serialization.SerializeObjectToXml(schedule.ActivityParams));
                scheduleRecord.ScheduleType = (int)schedule.ScheduleType;                
                scheduleRecord.ScheduleStartDate = schedule.ScheduleStartDate;
                scheduleRecord.ScheduleFinishDate = schedule.ScheduleFinishDate;
                scheduleRecord.ScheduleCanExpire = schedule.ScheduleCanExpire;
                scheduleRecord.RepeatEvery = schedule.RepeatEvery.Ticks;
                scheduleRecord.RunOnWeekDays = (int)schedule.RunOnWeekDays;
                scheduleRecord.RunOnMonths = (int)schedule.RunOnMonths;
                scheduleRecord.RunOnDays = (int)schedule.RunOnDays;
                scheduleRecord.RunOnDayOccurences = (int)schedule.RunOnDayOccurences;
                scheduleRecord.Active = schedule.Active;
                scheduleRecord.Created = timeStamp;
                scheduleRecord.Modified = timeStamp;
                scheduleRecord.ScheduleNextRunDate = schedule.ScheduleNextRunDate;
                scheduleRecord.ScheduleLastRunDate = schedule.ScheduleLastRunDate;
                schedule.LastError = schedule.LastError;
                this.DataAccess.ActivitySchedules.InsertOnSubmit(scheduleRecord);
                this.DataAccess.SubmitChanges();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Id"></param>
        public void RemoveVisionScheduleById(int Id)
        {
            Entities.DataAccess.ActivitySchedule scheduleRecord = (from a in this.DataAccess.ActivitySchedules where a.ScheduleId == Id select a).Single();
            if (scheduleRecord != null){
                this.DataAccess.ActivitySchedules.DeleteOnSubmit(scheduleRecord);
                this.DataAccess.SubmitChanges();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="scheduleNextRunDate"></param>
        /// <returns></returns>
        public List<Entities.Scheduling.VisionActvitySchedule> PollVisionSchedules(DateTime scheduleNextRunDate)
        {
            List<Entities.Scheduling.VisionActvitySchedule> schedules = new List<Entities.Scheduling.VisionActvitySchedule>();
            var scheduleRecords = this.DataAccess.ActivitySchedules.Where(s => s.Active == true && (s.ScheduleNextRunDate == null || s.ScheduleNextRunDate <= scheduleNextRunDate));
            foreach (var scheduleRecord in scheduleRecords){
                schedules.Add(this.VisionActvityScheduleRecordToVisionActvityScheduleEntity(scheduleRecord));    
            }
            return schedules;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="schedule"></param>
        /// <returns></returns>
        public DateTime ComputeNextRunTime(Entities.Scheduling.VisionActvitySchedule schedule)
        {
            DateTime timeStamp = DateTime.Now;
            DateTime baseDate = this.GetBaseDate(timeStamp, schedule);
            DateTime nextDate = this.GetNextDate(timeStamp, baseDate, schedule);                        
            return nextDate;
        }
                                
        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            this.DataAccess.Dispose();   
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <param name="schedule"></param>
        /// <returns></returns>
        private DateTime GetBaseDate(DateTime timeStamp, Entities.Scheduling.VisionActvitySchedule schedule)
        {
            DateTime returnDate = new DateTime();
            if (schedule.ScheduleNextRunDate.HasValue){
                returnDate = schedule.ScheduleNextRunDate.Value;
            }
            else{
                switch (schedule.ScheduleType){
                    case Entities.Scheduling.ScheduleTypes.Daily:
                        returnDate = new DateTime(timeStamp.Year, timeStamp.Month, timeStamp.Day, schedule.ScheduleStartDate.Hour, schedule.ScheduleStartDate.Minute, schedule.ScheduleStartDate.Second);
                        break;
                    case Entities.Scheduling.ScheduleTypes.Weekly:
                        DateTime cycleDate;
                        int dayCounter = 0;
                        while (true){
                            cycleDate = schedule.ScheduleStartDate.AddDays(dayCounter);
                            if (ValidScheduleDate(cycleDate, schedule.RunOnWeekDays)) { break; }
                            dayCounter++;                            
                        }
                        returnDate = new DateTime(cycleDate.Year, cycleDate.Month, cycleDate.Day, schedule.ScheduleStartDate.Hour, schedule.ScheduleStartDate.Minute, schedule.ScheduleStartDate.Second);
                        break;
                    case Entities.Scheduling.ScheduleTypes.Monthly:
                        throw new Exception("Monthly not ready yet");                        
                }
            }
            
            return returnDate;
        }
                
        /// <summary>
        /// 
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <param name="baseDate"></param>
        /// <param name="schedule"></param>
        /// <returns></returns>
        private DateTime GetNextDate(DateTime timeStamp, DateTime baseDate, Entities.Scheduling.VisionActvitySchedule schedule)
        {
            DateTime returnDate = new DateTime();            
            bool doGetNextDate = true;
            
            if (schedule.RepeatEvery.Ticks > 0 && baseDate.Date == timeStamp.Date){
                doGetNextDate = false;
                DateTime repeatDate = baseDate;
                while (true){
                    repeatDate = repeatDate.AddTicks(schedule.RepeatEvery.Ticks);
                    if(repeatDate > timeStamp){
                        returnDate = repeatDate;
                        break;
                    }
                }                                
                
                if (returnDate.Date > timeStamp.Date){
                    baseDate = new DateTime(baseDate.Year, baseDate.Month, baseDate.Day, schedule.ScheduleStartDate.Hour, schedule.ScheduleStartDate.Minute, schedule.ScheduleStartDate.Second);
                    doGetNextDate = true;
                }
            }
            
            if (schedule.RepeatEvery.Ticks > 0 && baseDate.Date < timeStamp.Date){
                doGetNextDate = true;
                baseDate = new DateTime(baseDate.Year, baseDate.Month, baseDate.Day, schedule.ScheduleStartDate.Hour, schedule.ScheduleStartDate.Minute, schedule.ScheduleStartDate.Second);
            }

            if (doGetNextDate){
                switch (schedule.ScheduleType){
                    case Entities.Scheduling.ScheduleTypes.Daily:                                                
                        returnDate = baseDate.AddDays(1);
                        if (baseDate > timeStamp){
                            returnDate = baseDate;
                        }
                        break;
                    case Entities.Scheduling.ScheduleTypes.Weekly:
                        DateTime cycleDate;
                        int dayCounter = 0;
                        while (true){
                            cycleDate = schedule.ScheduleStartDate.AddDays(dayCounter);
                            if (ValidScheduleDate(cycleDate, schedule.RunOnWeekDays)){
                                if (cycleDate > timeStamp){
                                    break;
                                }
                            }
                            dayCounter++;
                        }
                        returnDate = cycleDate;
                        break;
                    case Entities.Scheduling.ScheduleTypes.Monthly:
                        throw new Exception("Monthly not ready yet");                                                
                }
            }

            return returnDate;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="date"></param>
        /// <param name="scheduleWeekDays"></param>
        /// <returns></returns>
        private bool ValidScheduleDate(DateTime date, Entities.Scheduling.ScheduleWeekDays scheduleWeekDays)
        {
            bool valid = false;

            Entities.Scheduling.ScheduleWeekDays swd = Entities.Scheduling.ScheduleWeekDays.None;

            switch (date.DayOfWeek)
            {
                case DayOfWeek.Sunday:
                    swd = Entities.Scheduling.ScheduleWeekDays.Sunday;
                    break;
                case DayOfWeek.Monday:
                    swd = Entities.Scheduling.ScheduleWeekDays.Monday;
                    break;
                case DayOfWeek.Tuesday:
                    swd = Entities.Scheduling.ScheduleWeekDays.Tuesday;
                    break;
                case DayOfWeek.Wednesday:
                    swd = Entities.Scheduling.ScheduleWeekDays.Wednesday;
                    break;
                case DayOfWeek.Thursday:
                    swd = Entities.Scheduling.ScheduleWeekDays.Thursday;
                    break;
                case DayOfWeek.Friday:
                    swd = Entities.Scheduling.ScheduleWeekDays.Friday;
                    break;
                case DayOfWeek.Saturday:
                    swd = Entities.Scheduling.ScheduleWeekDays.Saturday;
                    break;
            }

            if (scheduleWeekDays.HasFlag(swd))
            {
                valid = true;
            }

            return valid;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="record"></param>
        /// <returns></returns>
        private Entities.Scheduling.VisionActvitySchedule VisionActvityScheduleRecordToVisionActvityScheduleEntity(Entities.DataAccess.ActivitySchedule record)
        {
            return new Entities.Scheduling.VisionActvitySchedule(){
                Active = record.Active,
                ActivityKey = record.ActivityKey,
                ActivityQueue = record.ActivityQueue,
                ActivityParams = Common.Serialization.DeserializeObjectFromXml<Entities.Activities.VisionActivityParameterCollection>(record.ActivityParams.CreateReader()),
                Created = record.Created,
                Description = record.Description,
                Id = record.ScheduleId,
                Key = record.Key,
                Modified = record.Modified,
                RepeatEvery = TimeSpan.FromTicks(record.RepeatEvery.Value),
                RunOnDayOccurences = (Entities.Scheduling.ScheduleRunOnDayOccurences)record.RunOnDayOccurences,
                RunOnDays = record.RunOnDays.Value,
                RunOnMonths = (Entities.Scheduling.ScheduleMonths)record.RunOnMonths,
                RunOnWeekDays = (Entities.Scheduling.ScheduleWeekDays)record.RunOnWeekDays,
                ScheduleCanExpire = record.ScheduleCanExpire.Value,
                ScheduleFinishDate = record.ScheduleFinishDate.Value,
                ScheduleStartDate = record.ScheduleStartDate.Value,
                ScheduleType = (Entities.Scheduling.ScheduleTypes)record.ScheduleType,
                ScheduleNextRunDate = record.ScheduleNextRunDate,
                ScheduleLastRunDate = record.ScheduleLastRunDate,
                LastError = record.LastError
            };
        }                
    }
}
